<?php
namespace Core\Libs\Crypt;

class Crypt {

	private static $_this = NULL;
	protected $key = NULL;
	protected $data = NULL;
	protected $algorithmToHash = 'md5';
	protected $algorithmToCrypt = MCRYPT_RIJNDAEL_256;
	protected $mode = MCRYPT_MODE_ECB;
	protected $rand = MCRYPT_RAND;
	protected $vector = NULL;
	protected $base64 = TRUE;
	protected $base64Loop = 1;
	protected $url = TRUE;
	protected $encoding = 'UTF-8';

	public function __construct() {

		self::$_this = $this;
		$this->key = NULL;
		$this->data = NULL;
		$this->algorithmToHash = ALGORITHM_TO_HASH;
		$this->algorithmToCrypt = MCRYPT_RIJNDAEL_256;
		$this->mode = MCRYPT_MODE_ECB;
		$this->rand = MCRYPT_RAND;
		$this->vector = NULL;
		$this->encoding = 'UTF-8';
		$this->base64Loop = \Core\Uses\Ler('Core.security.base64.loop');

		if($this->base64Loop === FALSE) {

			$this->base64Loop = 1;
		}
	}

	final public function setAlgorithmToCrypt($algorithm = MCRYPT_RIJNDAEL_256) {

		if(!empty($algorithm) || is_numeric($algorithm)) {

			$this->algorithmToCrypt = $algorithm;
		}

		return $this;
	}

	final public function getAlgorithmToCrypt() {

		return $this->algorithmToCrypt;
	}

	final public function setMode($mode = MCRYPT_MODE_ECB) {

		if(!empty($mode) || is_numeric($mode)) {

			$this->algorithmToCrypt = $mode;
		}

		return $this;
	}

	final public function getMode() {

		return $this->mode;
	}

	final public function setRand($rand = MCRYPT_RAND) {

		if(!empty($rand) || is_numeric($rand)) {

			$this->rand = $rand;
		}

		return $this;
	}

	final public function getRand() {

		$this->rand;
	}

	final public function setEncoding($encoding = 'UTF-8') {

		$this->encoding = $encoding;
		return $this;
	}

	final public function getEncoding() {

		return $this->encoding;
	}

	final public function setBase64($base64 = TRUE) {

		$this->base64 = $base64;
		return $this;
	}

	final public function getBase64() {

		return $this->base64;
	}

	final public function setUrl($url = TRUE) {

		$this->url = $url;
		return $this;
	}

	final public function getUrl() {

		return $this->url;
	}

	final public function setBase64Loop($loop = 1) {

		$this->base64Loop = $loop;
		return $this;
	}

	final public function getBase64Loop() {

		return $this->base64Loop;
	}

	final public static function setAlgorithmToHash($algorithm = NULL) {

		if(!empty($algorithm)) {

			self::$_this->algorithmToHash = $algorithm;
		}

		else {

			self::$_this->algorithmToHash = self::getAlgorithmToHash();
		}

		return self::$_this;
	}

	final public static function getAlgorithmToHash() {

		if(empty(self::$_this->algorithmToHash)) {

			if(!defined('ALGORITHM_TO_HASH')) {

				$algorithm = \Core\Uses\Ler('Core.security.hash.algorithm');

				if(empty($algorithm) || !$algorithm) {

					$arrayAlgorithms = \Core\Uses\Ler('Core.security.hash.algorithms', TRUE, 'array');
					$arrayAlgorithms = $arrayAlgorithms['_value'];
					$arrayAlgorithmsDefault = hash_algos();

					foreach($arrayAlgorithms as $algorithm){

						if(in_array($algorithm, $arrayAlgorithmsDefault)) {

							unset($arrayAlgorithmsDefault, $arrayAlgorithms);
							define('ALGORITHM_TO_HASH', $algorithm);
							break;
						}
					}
				}

				define('ALGORITHM_TO_HASH', $algorithm);
			}

			self::$_this->algorithmToHash = ALGORITHM_TO_HASH;
			return ALGORITHM_TO_HASH;
		}

		return self::$_this->algorithmToHash;
	}

	final public function getKey() {

		return $this->key;
	}

	final public function getKeyHash($key = NULL) {

		if(!empty($key)) {

			return md5($key);
		}

		return md5($this->key);
	}

	final public function getData() {

		return $this->data;
	}

	final public static function restoreAlgorithmToHash() {

		self::$_this->algorithmToHash = ALGORITHM_TO_HASH;
		return self::$_this;
	}

	final public static function getHash($hash = ALGORITHM_TO_HASH, $data = NULL, $output = FALSE) {

		$numArgs =& func_num_args();

		if($numArgs === 1) {

			$hash = self::getAlgorithmToHash();
			$data =& func_get_arg(0);
		}

		elseif($numArgs === 2) {

			$hash =& func_get_arg(0);
			$data =& func_get_arg(1);
		}

		if(!empty($hash) && !empty($data)) {

			return hash(&$hash, &$data, &$output);
		}

		return FALSE;
	}

	final public function characterEncoding($data = NULL) {

		try {

			$data =& mb_convert_encoding(&$data, $this->encoding);
		}

		catch(\Exception $e) {

			throw \Core\Uses\Exception('Error: Erro na codificação da string: ' . $data, E_WARNING, 100, __FILE__, __LINE__);
		}

		return $data;
	}

	final protected function base64Crypt($data = NULL, $option = 'encode', $base64Loop = 0) {

		if($option === 'encode') {

			while($base64Loop > 0) {

				$data =& base64_encode(&$data);
				$base64Loop--;
			}
		}

		elseif($option === 'decode') {

			while($base64Loop > 0) {

				$data =& base64_decode(&$data);
				$base64Loop--;
			}
		}

		return $data;
	}

	final protected function base64Init($data = NULL, $option = 'encode', $base64Loop = NULL) {

		if($this->base64 || !empty($base64Loop)) {

			if(empty($base64Loop)) {

				$base64Loop = $this->base64Loop;
			}

			$data =& $this->base64Crypt(&$data, $option, $base64Loop);
		}

		return $data;
	}

	final protected function urlEncode($data = NULL) {

		if($this->url) {

			return urlencode(&$data);
		}

		return $data;
	}

	final protected function urlDecode($data = NULL) {

		if($this->url) {

			return urldecode(&$data);
		}

		return $data;
	}

	final protected function initVector() {

		try {

			$this->vector =& mcrypt_create_iv(mcrypt_get_iv_size($this->algorithmToCrypt, $this->mode), $this->rand);
		}

		catch(\Exception $e) {

			throw \Core\Uses\Exception('Error: Problema ao iniciar o vetor para criptografia.', E_WARNING, 50, __FILE__, __LINE__);
		}
	}
}

class Encrypt extends Crypt {

	private $hashOfData = NULL;
	private $crcOfData = NULL;
	private $status = FALSE;

	final public function getHashOfData() {

		return $this->hashOfData;
	}

	final public function getStatus() {

		return $this->status;
	}

	public function __construct() {

		$this->data = NULL;
		$this->hashOfData = NULL;
		$this->crcOfData = NULL;
		$this->key = NULL;
		$this->headers = '{}';
		$this->status = FALSE;
		parent::__construct();
	}

	private function getHeaders() {

		$this->headers = '{' . $this->restoreAlgorithmToHash()->getAlgorithmToHash() . '=' . $this->hashOfData . ':' . 'crc32' . '=' . $this->crcOfData . '}';
		return $this->headers;
	}

	final public function start($data = NULL, $key = NULL) {

		if(!empty($data)) {

			if(empty($key)) {

				$key = \Core\Uses\Ler('core.security.salt');

				if(empty($key)) {

					return FALSE;
				}
			}

			$this->hashOfData = parent::getHash($data);
			$this->crcOfData = crc32($data);
			$data = $this->getHeaders() . $data;

			if($this->encoding !== FALSE) {

				$data =& parent::characterEncoding($data);
			}

			parent::initVector();

			try {

				$this->key = $key;
				$data =& parent::base64Init($data, 'encode');
				$key = parent::getKeyHash($key);
				$data =& mcrypt_encrypt($this->algorithmToCrypt, $key, &$data, $this->mode, $this->vector);
				$data =& parent::base64Init($data, 'encode');
				$data =& $this->urlEncode(&$data);
				$this->status = TRUE;
			}

			catch(\Exception $e) {

				$this->status = FALSE;

				throw \Core\Uses\Exception('Error: Problema ao criptografar os dados. <br> <b>Internal Error:</b> ' . $e->getMessage(), E_ERROR, 100, __FILE__, __LINE__);
			}

			$this->data = $data;
			unset($data);
		}

		else {

			$this->status = FALSE;
			$this->data = FALSE;
		}	

		return $this->data;
	}
}

class Decrypt extends Crypt {

	private $hashOfData = NULL;
	private $hashOfHeader = NULL;
	private $algorithmOfHashToHeader = NULL;
	private $crcOfData = NULL;
	private $crcOfHeader = NULL;
	private $isTrusted = FALSE;
	private $headers = '{}';
	private $status = FALSE;

	final public function getHashOfData() {

		return $this->hashOfData;
	}

	final public function getCrcOfData() {

		return $this->crcOfData;
	}

	final public function getHashOfHeader() {

		return $this->hashOfHeader;
	}

	final public function getAlgorithmOfHashToHeader() {

		return $this->algorithmOfHashToHeader;
	}

	final public function getCrcOfHeader() {

		return $this->crcOfHeader;
	}

	final public function getHeaders() {

		return $this->headers;
	}

	final public function isTrusted() {

		return $this->isTrusted;
	}

	final public function getStatus() {

		return $this->status;
	}

	public function __construct() {

		$this->data = NULL;
		$this->hashOfData = NULL;
		$this->algorithmOfHashToHeader = NULL;
		$this->hashOfHeader = NULL;
		$this->crcOfData = NULL;
		$this->crcOfHeader = NULL;
		$this->key = NULL;
		$this->isTrusted = FALSE;
		$this->headers = '{}';
		$this->status = FALSE;
		parent::__construct();
	}

	private function checkTrust() {

		if(($this->hashOfHeader === $this->hashOfData) && ($this->crcOfHeader === $this->crcOfData)) {

			$this->isTrusted = TRUE;
		}

		else {

			$this->isTrusted = FALSE;
		}
	}

	private function setHeaders() {

		$headers =& explode(':', substr(&$this->headers, 1, -1), 2);
		list($this->algorithmOfHashToHeader, $this->hashOfHeader) = explode('=', &$headers[0]);
		$headers =& explode('=', &$headers[1]);
		$this->crcOfHeader = (int)$headers[1];
	}

	final public function start($data = NULL, $key = NULL) {

		if(!empty($data)) {

			if(empty($key)) {

				$key = \Core\Uses\Ler('core.security.salt');

				if(empty($key)) {

					return FALSE;
				}
			}

			parent::initVector();

			try {

				$this->key = $key;
				$data =& parent::urlDecode(&$data);
				$data =& parent::base64Init(&$data, 'decode');
				$key = parent::getKeyHash($key);
				$data =& mcrypt_decrypt($this->algorithmToCrypt, $key, &$data, $this->mode, $this->vector);
				$data =& parent::base64Init(&$data, 'decode');
				$this->status = TRUE;
			}

			catch(\Exception $e) {

				$this->status = FALSE;
				$this->data = FALSE;
				$this->isTrusted = FALSE;

				throw \Core\Uses\Exception('Error: Problema ao descriptografar os dados. <br> <b>Internal Error:</b> ' . $e->getMessage(), E_ERROR, 100, __FILE__, __LINE__);
			}

			if($this->encoding !== FALSE) {

				$data = parent::characterEncoding(&$data);
			}

			$match = array();

			if(preg_match('/^\{.*\}/i', &$data, &$match)) {

				$this->headers = $match[0];
				$this->setHeaders();
				$data =& preg_replace('/^\{.*\}/i', '', &$data);
			}

			$this->hashOfData = parent::getHash($data);
			$this->crcOfData = crc32($data);
			$this->checkTrust();
			$this->data = $data;
			unset($data);
		}

		else {

			$this->data = FALSE;
			$this->status = FALSE;
			$this->isTrusted = FALSE;
		}

		return $this->data;
	}
}
?>